﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace bittlucky.Hearing_Timer
{

    public partial class Form1 : Form
    {
        #region Constants
        private const uint _maxOvertimeInHours = 3;
        private const uint _maxSnoozeTimeInSeconds = 120;

        private const int _originToLabelHoursDeltaY = 0;
        private const int _originToTextBoxHoursDeltaY = 26;
        private const int _originToButtonIncrementHoursDeltaY = 103;
        private const int _originToButtonStartStopDeltaY = 146;
        private const int _originToComboBoxWarningTimeDeltaY = 221;
        
        private const int _textBoxToTextBoxDeltaX = 128;
        private const int _textBoxToSeparatorLabelDeltaX = 81;
        private const int _buttonIncrementToButtonDecrementDeltaX = 52;
        private const int _buttonDecrementToButtonIncrementDeltaX = 75;
        private const int _buttonStartStopToButtonResetSnoozeDeltaX = 181;
        private const int _comboBoxWarningTimeToLabelWarningSizeDeltaX = 156;
        private const int _comboBoxWarningTimeToLabelWarningSizeDeltaY = 3;
        private const int _labelWarningSizeToComboBoxWarningSizeDeltaX = 77;
        private const int _baseControlWidth = 75;

        private const int _totalUIHeight = 192;
        private const int _totalUIWidth = 331;

        private const string _startText = "Start";
        private const string _stopText = "Stop";
        private const string _snoozeText = "Snooze";
        private const string _resetText = "Reset";

        private const bool _enableControls = true;
        private const bool _disableControls = false;
        #endregion

        #region Enums
        enum TimerStatus { initializingStatus, inputStatus, timingStatus, warningStatus, overtimeStatus }
        enum WarningStatus { notWarning, needsWarning, isWarning }
        enum WarningSize { small, medium, large }
        #endregion

        #region Local Variables
        bool _windowInitialized;
        uint _hours;
        uint _minutes;
        uint _seconds;
        uint _secondsElapsedSinceSnooze;
        TimerStatus _timerStatus;
        WarningStatus _warningStatus;
        #endregion

        public Form1()
        {
            InitializeComponent();
            InitializeVariables();
            InitializeWindow();
            ConfigureForInput();
        }

        #region Actions

        private void StartTimer()
        {
            _secondsElapsedSinceSnooze = 0;
            ConfigureTimeValuesFromInputs();
            SaveToSettings();
            ConfigureForTiming();
            timer1.Start();
            UpdateStatus();
        }

        private void StopTimer()
        {
            InitializeVariables();
            ConfigureForInput();
            timer1.Stop();
        }

        private void SnoozeTimer()
        {
            _secondsElapsedSinceSnooze = 0;
            _warningStatus = WarningStatus.notWarning;
            this.TopMost = false;
            this.WindowState = FormWindowState.Minimized;
        }

        private void ResetTimer()
        {
            textBoxSeconds.Text = "00";
            textBoxMinutes.Text = "00";
            textBoxHours.Text = "00";
        }
        #endregion

        #region UI configuration

        private void ConfigureUI()
        {
            ConfigureForm();
            ConfigureBackColor();
            ConfigureControls();
            UpdateNumberDisplay();
        }

        private void ConfigureForInput()
        {
            _timerStatus = TimerStatus.inputStatus;
            ConfigureUI();
        }

        private void ConfigureForTiming()
        {
            _timerStatus = TimerStatus.timingStatus;
            _warningStatus = WarningStatus.notWarning;
            ConfigureUI();
        }

        private void ConfigureForWarning()
        {
            _timerStatus = TimerStatus.warningStatus;
            ConfigureUI();
        }

        private void ConfigureForOvertime()
        {
            _timerStatus = TimerStatus.overtimeStatus;
            ConfigureUI();
        }

        private void ConfigureBackColor()
        {
            switch (_timerStatus)
            {
                case TimerStatus.overtimeStatus:
                    this.BackColor = Color.Red;
                    break;
                case TimerStatus.warningStatus:
                    this.BackColor = Color.Yellow;
                    break;
                case TimerStatus.timingStatus:
                    this.BackColor = Color.Green;
                    break;
                default:
                    this.BackColor = SystemColors.Control;
                    break;
            }
        }

        private void ConfigureForm()
        {
            //this.Visible = false;
            this.WindowState = FormWindowState.Normal;
            WarningSize warningSize = (WarningSize)comboBoxWarningSize.SelectedIndex;
            switch (_timerStatus)
            {
                case TimerStatus.overtimeStatus:
                case TimerStatus.warningStatus:
                    this.TopMost = true;
                    switch (warningSize)
                    {
                        case WarningSize.small:
                            this.Size = Properties.Settings.Default.SmallWindowSize;
                            this.WindowState = FormWindowState.Normal;
                            break;
                        case WarningSize.medium:
                            this.Size = Properties.Settings.Default.LargeWindowSize;
                            this.WindowState = FormWindowState.Normal;
                            break;
                        default:
                            this.WindowState = FormWindowState.Maximized;
                            break;
                    }
                    break;
                case TimerStatus.initializingStatus:
                case TimerStatus.inputStatus:
                case TimerStatus.timingStatus:
                default:
                    this.TopMost = false;
                    this.Size = Properties.Settings.Default.SmallWindowSize;
                    this.WindowState = FormWindowState.Normal;
                    break;
            }
            //this.Visible = true;
        }

        private void ConfigureControls()
        {
            bool isInputting = false;
            if (_timerStatus == TimerStatus.inputStatus)
            {
                isInputting = true;
            }
            else
            {
                isInputting = false;
            }
            buttonStartStop.Text = isInputting ? _startText : _stopText;
            buttonResetSnooze.Text = isInputting ? _resetText : _snoozeText;
            textBoxHours.Enabled = isInputting;
            textBoxMinutes.Enabled = isInputting;
            textBoxSeconds.Enabled = isInputting;
            buttonIncreaseHours.Visible = isInputting;
            buttonDecreaseHours.Visible = isInputting;
            buttonIncreaseMinutes.Visible = isInputting;
            buttonDecreaseMinutes.Visible = isInputting;
            buttonIncreaseSeconds.Visible = isInputting;
            buttonDecreaseSeconds.Visible = isInputting;
            comboBoxWarningSize.Visible = isInputting;
            labelWarningSize.Visible = isInputting;
            comboBoxWarningTime.Visible = isInputting;
        }

        private void ConfigureTimeValuesFromInputs()
        {
            _hours = textBoxHours.Text.Length > 0 ? Convert.ToUInt32(textBoxHours.Text) : 0;
            _minutes = textBoxMinutes.Text.Length > 0 ? Convert.ToUInt32(textBoxMinutes.Text) : 0;
            _seconds = textBoxSeconds.Text.Length > 0 ? Convert.ToUInt32(textBoxSeconds.Text) : 0;
        }

        #endregion

        #region Timer functions

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (_timerStatus == TimerStatus.overtimeStatus)
            {
                IncrementTimer();
            }
            else
            {
                DecrementTimer();
            }
            UpdateNumberDisplay();
            UpdateStatus();
            UpdateBackColor();
            WarnUser();
        }

        private void DecrementTimer()
        {
            if (_seconds > 0)
            {
                _seconds--;
            }
            else if (_minutes > 0)
            {
                _minutes--;
                _seconds = 59;
            }
            else if (_hours > 0)
            {
                _hours--;
                _minutes = 59;
                _seconds = 59;
            }
            else
            {
                IncrementTimer();
            }
        }

        private void IncrementTimer()
        {
            _secondsElapsedSinceSnooze++;
            if (++_seconds > 59)
            {
                _seconds = 0;
                if (++_minutes > 59)
                {
                    _minutes = 0;
                    if (++_hours == _maxOvertimeInHours)
                    {
                        StopTimer();
                    }
                }
            }
        }

        private String PaddedStringFromUInt(uint number)
        {
            return number.ToString().PadLeft(2, '0');
        }

        #endregion

        #region UI update

        private void UpdateNumberDisplay()
        {
            textBoxSeconds.Text = PaddedStringFromUInt(_seconds);
            textBoxMinutes.Text = PaddedStringFromUInt(_minutes);
            textBoxHours.Text = PaddedStringFromUInt(_hours);
        }

        private void UpdateStatus()
        {
            if (HasTimeExceededSnooze() && _warningStatus != WarningStatus.isWarning)
            {
                _warningStatus = WarningStatus.needsWarning;
            }
            if (HasTimeExpired())
            {
                _timerStatus = TimerStatus.overtimeStatus;
                _warningStatus = WarningStatus.needsWarning;
            }
            if (HasTimePassedWarning())
            {
                _timerStatus = TimerStatus.warningStatus;
                _warningStatus = WarningStatus.needsWarning;
            }
        }

        private void UpdateBackColor()
        {
            if (this.BackColor != Color.Yellow && _timerStatus == TimerStatus.warningStatus)
            {
                this.BackColor = Color.Yellow;
            }
            if (this.BackColor != Color.Red && _timerStatus == TimerStatus.overtimeStatus)
            {
                this.BackColor = Color.Red;
            }
            if (_warningStatus == WarningStatus.isWarning && _timerStatus == TimerStatus.warningStatus)
            {
                if (_seconds % 2 == 0)
                {
                    this.BackColor = Color.Yellow;
                }
                else
                {
                    this.BackColor = Color.LightYellow;
                }
            }
            if (_warningStatus == WarningStatus.isWarning && _timerStatus == TimerStatus.overtimeStatus)
            {
                if (_seconds % 2 == 0)
                {
                    this.BackColor = Color.Red;
                }
                else
                {
                    this.BackColor = Color.Tomato;
                }
            }
        }

        #endregion

        #region Warnings

        private void WarnUser()
        {
            if (_warningStatus != WarningStatus.needsWarning)
            {
                return;
            }
            _warningStatus = WarningStatus.isWarning;
            ConfigureForm();
        }

        private bool HasTimeExceededSnooze()
        {
            return _secondsElapsedSinceSnooze >= _maxSnoozeTimeInSeconds;
        }

        private bool HasTimeExpired()
        {
            return _hours == 0 && _minutes == 0 && _seconds == 0;
        }

        private bool HasTimePassedWarning()
        {
            if (_timerStatus != TimerStatus.timingStatus) { return false; }
            if (_hours != 0) { return false; }
            return  _minutes < comboBoxWarningTime.SelectedIndex || 
                    (_minutes == comboBoxWarningTime.SelectedIndex && _seconds == 0);
        }

        #endregion

        #region Number entry

        private void actionButton_Click(object sender, EventArgs e)
        {
            Button button = sender as Button;
            if (button.Text == _startText)
            {
                StartTimer();
            }
            else if(button.Text == _snoozeText)
            {
                SnoozeTimer();
            }
            else if (button.Text == _stopText)
            {
                StopTimer();
            }
            else if (button.Text == _resetText)
            {
                ResetTimer();
            }
        }

        private void input_Enter(object sender, EventArgs e)
        {
            TextBox textBox = sender as TextBox;
            HighlightTextBox(textBox, true);
            textBox.SelectAll();
        }

        private void input_Leave(object sender, EventArgs e)
        {
            TextBox textBox = sender as TextBox;
            String text = textBox.Text;
            if (text == "" || text.Length == 0 || text.Length > 2)
            {
                textBox.Text = "00";
            }
            else
            {
                int number = Convert.ToInt32(textBox.Text);
                if (number < 0)
                {
                    number = 0;
                }
                if (number > 59 && textBox != textBoxHours)
                {
                    number = 59;
                }
                if (number > 23 && textBox == textBoxHours)
                {
                    number = 23;
                }
                textBox.Text = number.ToString().PadLeft(2, '0');
            }
            HighlightTextBox(textBox, false);
        }

        private void input_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar))
            {
                e.Handled = true;
            }
        }

        private void increaseHoursButton_Click(object sender, EventArgs e)
        {
            uint hours = Convert.ToUInt32(textBoxHours.Text);
            hours = hours < 23 ? hours + 1 : 0;
            textBoxHours.Text = hours.ToString().PadLeft(2, '0');
        }

        private void decreaseHoursButton_Click(object sender, EventArgs e)
        {
            uint hours = Convert.ToUInt32(textBoxHours.Text);
            hours = hours > 0 ? hours - 1 : 23;
            textBoxHours.Text = hours.ToString().PadLeft(2, '0');
        }

        private void increaseMinutesButton_Click(object sender, EventArgs e)
        {
            uint minutes = Convert.ToUInt32(textBoxMinutes.Text);
            minutes = minutes < 59 ? minutes + 1 : 0;
            textBoxMinutes.Text = minutes.ToString().PadLeft(2, '0');
        }

        private void decreaseMinutesButton_Click(object sender, EventArgs e)
        {
            uint minutes = Convert.ToUInt32(textBoxMinutes.Text);
            minutes = minutes > 0 ? minutes - 1 : 59;
            textBoxMinutes.Text = minutes.ToString().PadLeft(2, '0');
        }

        private void increaseSecondsButton_Click(object sender, EventArgs e)
        {
            uint seconds = Convert.ToUInt32(textBoxSeconds.Text);
            seconds = seconds < 59 ? seconds + 1 : 0;
            textBoxSeconds.Text = seconds.ToString().PadLeft(2, '0');
        }

        private void decreaseSecondsButton_Click(object sender, EventArgs e)
        {
            uint seconds = Convert.ToUInt32(textBoxSeconds.Text);
            seconds = seconds > 0 ? seconds - 1 : 59;
            textBoxSeconds.Text = seconds.ToString().PadLeft(2, '0');
        }

        private void resetButton_Click(object sender, EventArgs e)
        {
            ResetTimer();
        }

        private void highlight(object sender, MouseEventArgs e)
        {
            Button button = sender as Button;
            if (button == buttonIncreaseHours || button == buttonDecreaseHours)
            {
                HighlightTextBox(textBoxHours, true);
            }
            if (button == buttonIncreaseMinutes || button == buttonDecreaseMinutes)
            {
                HighlightTextBox(textBoxMinutes, true);
            }
            if (button == buttonIncreaseSeconds || button == buttonDecreaseSeconds)
            {
                HighlightTextBox(textBoxSeconds, true);
            }
        }

        private void unhighlight(object sender, MouseEventArgs e)
        {
            Button button = sender as Button;
            if (button == buttonIncreaseHours || button == buttonDecreaseHours)
            {
                HighlightTextBox(textBoxHours, false);
            }
            if (button == buttonIncreaseMinutes || button == buttonDecreaseMinutes)
            {
                HighlightTextBox(textBoxMinutes, false);
            }
            if (button == buttonIncreaseSeconds || button == buttonDecreaseSeconds)
            {
                HighlightTextBox(textBoxSeconds, false);
            }
        }

        private void HighlightTextBox(TextBox textBox, bool shouldHighlight)
        {
            if (shouldHighlight)
            {
                textBox.BackColor = SystemColors.Info;
            }
            else
            {
                textBox.BackColor = SystemColors.Window;
            }
        }

#endregion

        #region Settings

        private void InitializeVariables()
        {
            _timerStatus = TimerStatus.initializingStatus;
            LoadValuesFromSettings();
        }

        private void LoadValuesFromSettings()
        {
            _hours = Properties.Settings.Default.hours;
            _minutes = Properties.Settings.Default.minutes;
            _seconds = Properties.Settings.Default.seconds;
            comboBoxWarningSize.SelectedIndex = Properties.Settings.Default.comboBoxWarningSizePickerSelectedIndex;
            comboBoxWarningTime.SelectedIndex = Properties.Settings.Default.comboBoxWarningTimePickerSelectedIndex;
        }

        private void SaveToSettings()
        {
            if (_timerStatus != TimerStatus.inputStatus)
            {
                return;
            }
            ConfigureTimeValuesFromInputs();
            Properties.Settings.Default.hours = _hours;
            Properties.Settings.Default.minutes = _minutes;
            Properties.Settings.Default.seconds = _seconds;
            Properties.Settings.Default.WindowPosition = this.DesktopBounds;
            Properties.Settings.Default.comboBoxWarningSizePickerSelectedIndex = comboBoxWarningSize.SelectedIndex;
            Properties.Settings.Default.comboBoxWarningTimePickerSelectedIndex = comboBoxWarningTime.SelectedIndex;
            Properties.Settings.Default.Save();
        }

        private void warningOptions_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (_timerStatus == TimerStatus.initializingStatus)
            {
                return;
            }
            SaveToSettings();
        }

        #endregion

        # region Window size/position
        // msorens: Added region to handle closing when window is minimized or maximized.

        private void InitializeWindow()
        {
            // this is the default
            this.WindowState = FormWindowState.Normal;
            this.StartPosition = FormStartPosition.WindowsDefaultBounds;

            // check if the saved bounds are nonzero and visible on any screen
            if (Properties.Settings.Default.WindowPosition != Rectangle.Empty &&
                IsVisibleOnAnyScreen(Properties.Settings.Default.WindowPosition))
            {
                // first set the bounds
                this.StartPosition = FormStartPosition.Manual;
                this.DesktopBounds = Properties.Settings.Default.WindowPosition;

                // afterwards set the window state to the saved value (which could be Maximized)
                this.WindowState = Properties.Settings.Default.WindowState;
            }
            else
            {
                // this resets the upper left corner of the window to windows standards
                this.StartPosition = FormStartPosition.WindowsDefaultLocation;

                // we can still apply the saved size
                // msorens: added gatekeeper, otherwise first time appears as just a title bar!
                if (Properties.Settings.Default.WindowPosition != Rectangle.Empty)
                {
                    this.Size = Properties.Settings.Default.WindowPosition.Size;
                }
            }
            LayoutForm();
            _windowInitialized = true;
        }

        private bool IsVisibleOnAnyScreen(Rectangle rect)
        {
            foreach (Screen screen in Screen.AllScreens)
            {
                if (screen.WorkingArea.IntersectsWith(rect))
                {
                    return true;
                }
            }

            return false;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            // only save the WindowState if Normal or Maximized
            switch (this.WindowState)
            {
                case FormWindowState.Normal:
                case FormWindowState.Maximized:
                    Properties.Settings.Default.WindowState = this.WindowState;
                    break;

                default:
                    Properties.Settings.Default.WindowState = FormWindowState.Normal;
                    break;
            }

            # region msorens: this code does *not* handle minimized/maximized window.

            // reset window state to normal to get the correct bounds
            // also make the form invisible to prevent distracting the user
            //this.Visible = false;
            //this.WindowState = FormWindowState.Normal;
            //Settings.Default.WindowPosition = this.DesktopBounds;

            # endregion

            SaveToSettings();
        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            PositionForm();
            LayoutForm();
            TrackWindowState();
        }

        private void Form1_Move(object sender, EventArgs e)
        {
            TrackWindowState();
        }

        // On a move or resize in Normal state, record the new values as they occur.
        // This solves the problem of closing the app when minimized or maximized.
        private void TrackWindowState()
        {
            // Don't record the window setup, otherwise we lose the persistent values!
            if (!_windowInitialized) { return; }
            if (_timerStatus != TimerStatus.inputStatus) { return; }

            if (WindowState == FormWindowState.Normal)
            {
                Properties.Settings.Default.WindowPosition = this.DesktopBounds;
            }
        }

        private void PositionForm()
        {
            if (this.WindowState == FormWindowState.Maximized ||
                this.WindowState == FormWindowState.Minimized)
            {
                return;
            }
            this.WindowState = FormWindowState.Normal;
            Size largeSize = Properties.Settings.Default.LargeWindowSize;
            Size smallSize = Properties.Settings.Default.SmallWindowSize;
            int deltaX = (largeSize.Width - smallSize.Width) / 2;
            int deltaY = (largeSize.Height - smallSize.Height) / 2;
            int x, y;
            if (this.Size == smallSize)
            {
                this.DesktopLocation = new Point(this.DesktopLocation.X + deltaX, this.DesktopLocation.Y + deltaY);
            }
            else
            {
                x = this.DesktopLocation.X > deltaX ? this.DesktopLocation.X - deltaX : 0;
                y = this.DesktopLocation.Y > deltaY ? this.DesktopLocation.Y - deltaY : 0;
                this.DesktopLocation = new Point(x, y);
            }
        }

        private void LayoutForm()
        {
            int centerX = (this.Width) / 2;
            int centerY = (this.Height - SystemInformation.CaptionHeight) / 2;
            int originX = centerX - ((_totalUIWidth) / 2);
            int originY = centerY - (_totalUIHeight / 2) - (labelHours.Height);
            int labelY = originY + _originToLabelHoursDeltaY;
            labelHours.Location = new Point(originX, labelY);
            labelMinutes.Location = new Point(labelHours.Location.X + _textBoxToTextBoxDeltaX, labelY);
            labelSeconds.Location = new Point(labelMinutes.Location.X + _textBoxToTextBoxDeltaX, labelY);
            int inputY = originY + _originToTextBoxHoursDeltaY;
            textBoxHours.Location = new Point(originX, inputY);
            textBoxMinutes.Location = new Point(textBoxHours.Location.X + _textBoxToTextBoxDeltaX, inputY);
            textBoxSeconds.Location = new Point(textBoxMinutes.Location.X + _textBoxToTextBoxDeltaX, inputY);
            labelHoursMinutesSeparator.Location = new Point(textBoxHours.Location.X + _textBoxToSeparatorLabelDeltaX, inputY);
            labelMinutesSecondsSeparator.Location = new Point(textBoxMinutes.Location.X + _textBoxToSeparatorLabelDeltaX, inputY);
            int incrementButtonY = originY + _originToButtonIncrementHoursDeltaY;
            buttonIncreaseHours.Location = new Point(originX, incrementButtonY);
            buttonDecreaseHours.Location = new Point(originX + _buttonIncrementToButtonDecrementDeltaX, incrementButtonY);
            buttonIncreaseMinutes.Location = new Point(buttonDecreaseHours.Location.X + _buttonDecrementToButtonIncrementDeltaX, incrementButtonY);
            buttonDecreaseMinutes.Location = new Point(buttonIncreaseMinutes.Location.X + _buttonIncrementToButtonDecrementDeltaX, incrementButtonY);
            buttonIncreaseSeconds.Location = new Point(buttonDecreaseMinutes.Location.X + _buttonDecrementToButtonIncrementDeltaX, incrementButtonY);
            buttonDecreaseSeconds.Location = new Point(buttonIncreaseSeconds.Location.X + _buttonIncrementToButtonDecrementDeltaX, incrementButtonY);
            int buttonY = originY + _originToButtonStartStopDeltaY;
            buttonStartStop.Location = new Point(originX, buttonY);
            buttonResetSnooze.Location = new Point(buttonStartStop.Location.X + _buttonStartStopToButtonResetSnoozeDeltaX, buttonY);
            int warningOptionsY = originY + _originToComboBoxWarningTimeDeltaY;
            comboBoxWarningTime.Location = new Point(originX, warningOptionsY);
            labelWarningSize.Location = new Point(comboBoxWarningTime.Location.X + _comboBoxWarningTimeToLabelWarningSizeDeltaX, 
                warningOptionsY + _comboBoxWarningTimeToLabelWarningSizeDeltaY);
            comboBoxWarningSize.Location = new Point(labelWarningSize.Location.X + _labelWarningSizeToComboBoxWarningSizeDeltaX, 
                warningOptionsY);
        }

        # endregion window size/position
    }
}
